package jsr292.weaver.opt;

import java.util.ArrayList;
import java.util.List;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;


public final class OptimizerVarAllocator {
    private final ArrayList<Var> vars;
    int slotCount;
    final boolean allowReplaceInPlace;

    public static class Var {
	final int slotIndex; // -1 if return value is not stored in a variable
	final Type type;

	Var(int slotIndex, Type type) {
	    this.slotIndex = slotIndex;
	    this.type = type;
	}
	
	public Type getType() {
            return type;
        }
	
	public void load(MethodVisitor mv) {
	    mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), slotIndex);
	}

	public void store(MethodVisitor mv) {
            mv.visitVarInsn(type.getOpcode(Opcodes.ISTORE), slotIndex);
	}
    }

    public OptimizerVarAllocator(boolean allowReplaceInPlace, int startIndex, int startSlot, Type... types) {
	ArrayList<Var> vars = new ArrayList<Var>();
	int slot = startSlot;
	for (int i=startIndex; i<types.length; i++) {
	    Type type = types[i];
	    vars.add(new Var(slot, type));
	    slot += type.getSize();
	}

	this.vars = vars;
	this.slotCount = slot;
	this.allowReplaceInPlace = allowReplaceInPlace;
    }
    
    public OptimizerVarAllocator(OptimizerVarAllocator allocator, boolean allowReplaceInPlace) {
	this.vars = new ArrayList<Var>(allocator.vars);
	this.slotCount = allocator.slotCount;
	this.allowReplaceInPlace = allowReplaceInPlace;
    }
    
    // empty allocator
    private OptimizerVarAllocator(OptimizerVarAllocator allocator, int slotCount) {
	this.vars = new ArrayList<Var>();
	this.slotCount = slotCount;
	this.allowReplaceInPlace = allocator.allowReplaceInPlace;
    }
    
    public OptimizerVarAllocator createEmptyAllocator(int slotCount) {
	return new OptimizerVarAllocator(this, slotCount);
    }

    public Var getVar(int index) {
	return vars.get(index);
    }
    
    public Var replaceVarType(int index, Type type) {
        int typeSize = type.getSize();
        Var var = getVar(index);
        int slotIndex;
        if (!allowReplaceInPlace || var.type.getSize() < typeSize) {
            slotIndex = slotCount;
            slotCount += typeSize;
        } else {
            slotIndex = var.slotIndex;
        }
        Var newVar = new Var(slotIndex, type);
        vars.set(index, newVar);
        return newVar;
    }
    
    /*
    public void relocateVar(int index) {
        int slotIndex = slotCount;
        Var var = getVar(index);
        Type type = var.type;
        slotCount += type.getSize();
        vars.set(index, new Var(slotIndex, type));
    }*/
    
    public List<Var> getVarList() {
	return vars;   //FIXME unmodifiable ?
    }
    
    public Var newVar(Type type) {
	Var var = newVar(type, slotCount);
	slotCount += type.getSize();
	return var;
    }
    
    public Var newVar(Type type, int slotIndex) {
	Var var = new Var(slotIndex, type);
	vars.add(var);
	return var;
    }

    public void drop(int position, int length) {
        //System.out.println("context drop: position: "+position+" length: "+length);
        
	//FIXME use removeRange instead
	for(int i=0; i<length; i++)
	    vars.remove(position);
    }
    
    //FIXME insert should not create another allocator
    public OptimizerVarAllocator insert(int index, Type... types) {
	OptimizerVarAllocator allocator = new OptimizerVarAllocator(this, allowReplaceInPlace);
	
	int slotCount = allocator.slotCount;
	int length = types.length;
	ArrayList<Var> cvars = allocator.vars;
	for(int i=0; i<length; i++) {
	    Type type = types[i];
	    Var var = new Var(slotCount, type);
	    slotCount += type.getSize();
            cvars.add(index + i, var);    
	}
	
	allocator.slotCount = slotCount;
	return allocator;
    }
}
